package fr.ing4.Macri.MonteCarlo.MultiThread;
import java.util.ArrayList;
import java.util.List;
import java.util.Observable;
import java.util.Observer;
import java.util.Random;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;


/**
 * 
 */

/**
 * @author enzo
 * 
 */
public final class MonteCarloSimulation extends Observable implements Runnable{
	
  private Observer viewToUpdate;
	
  private String callPutFlag;
  private int nSimu,nSteps;
  private double dt,st;
  private double sum,drift,vSqrdt;
  private Random random;
  private int z;
  private double x,r,t,v,s;
  private double b;
  private int dataChunk;
  private double e;
  private double res, time;
  private boolean finish=false;
  public double progress;
  
  public MonteCarloSimulation(Observer view)
	{
		this.viewToUpdate=view;
	}

  /*public MonteCarloSimulation(String callPutFlag, double s,
			double x, double t, double r, double b, double v,int nSimulations, int nSteps)
  {
	  this.nSimulations=nSimulations;
	  this.nSteps=nSteps;
	  this.st=s;
	  this.x=x;
	  this.t=t;
	  this.r=r;
	  this.b=b;
	  this.v=v;
	  this.callPutFlag=callPutFlag;
	  //random=new Random();
  }*/

	public double MonteCarloStandardOption(SimulationBean sb) {
		
		
		int i, j;
		long start,end;
		/*dt = t / nSteps;
		drift = (b - v * v / 2) * dt;
		vSqrdt = v * Math.sqrt(dt);
		if (callPutFlag.contentEquals("c"))
			z = 1;
		else if (callPutFlag.contentEquals("p"))
			z = -1;*/
		start=System.currentTimeMillis();
		e=nSimu/dataChunk;
		Random random = new Random();
		for (i = 1; i <= e; i++) {
			
			sb.setSt(sb.getS());
			for (j = 1; j <= sb.getnSteps(); j++) {
				sb.setSt(sb.getSt() * Math.exp(sb.getDrift() + sb.getvSqrdt() * random.nextGaussian()));
			}
			sb.setSum(sb.getSum() + Math.max(sb.getZ()* (sb.getSt() - sb.getX()), 0));
			this.progress=i*100/e;
			setChanged();
			 notifyObservers(this);
		}
		end=System.currentTimeMillis();
		long t=end-start;
		System.out.println("T time :" +t);
		return Math.exp(-sb.getR() * sb.getT()) * (sb.getSum() /( nSimu/dataChunk));
		
		//return MonteCarloSim();
		/*for (i = 1; i <= e; i++) {
			st = s;
			for (j = 1; j <= ns; j++) {
				st = st * Math.exp(drift + vsqrdt * random.nextGaussian());
			}
			sum = sum + Math.max(z * (st - x), 0);
		}
		return Math.exp(-r * t) * (sum / e);*/
	}
	public void initSimulation()
	{
		this.dt = this.t / this.nSteps;
		this.drift = (this.b - this.v * this.v / 2) * this.dt;
		this.vSqrdt = this.v * Math.sqrt(this.dt);
		this.st=s;
		if (this.callPutFlag.contentEquals("c"))
			this.z = 1;
		else if (this.callPutFlag.contentEquals("p"))
			this.z = -1;
		//default
		//this.dataChunk=Runtime.getRuntime().availableProcessors();

		System.out.println(" "+ callPutFlag+" "+dt+" "+st+" "+drift+" "+vSqrdt+" "+z+" "+x+" "+r+" "+t+" "+b+" "+v+" "+dataChunk);
		
	}
    
	public double MonteCarloSim()
	{
		initSimulation();
		final int numberOfCores = Runtime.getRuntime().availableProcessors();
		final double blockingCoefficient = 0.1;
		final int poolSize = (int)(numberOfCores / (1 - blockingCoefficient));
		System.out.println(" cores" + numberOfCores + " pool : "+ poolSize);
		final List<Callable<Double>> partitions = new ArrayList<Callable<Double>>();
		 for(int i=1;i<=dataChunk;i++)
		 {
			 partitions.add(new Callable<Double>() {
        public Double call() throws Exception {
        	SimulationBean sb = new SimulationBean(nSteps,dt,st,drift,vSqrdt,z,x,r,t);
			/*for (int j = 1; j <= sb.getnSteps(); j++) {
				sb.setSt(sb.getSt()*Math.exp(sb.getDrift() + sb.getvSqrdt() * random.nextGaussian()));
			}
			 double a =Math.max(sb.getZ() * (sb.getSt() - sb.getX()), 0);
			// System.out.println("" +a);
			 *
			 return a;*/
        	System.out.println(Thread.currentThread());
        	return MonteCarloStandardOption(sb);
        }        
      });
		 }
			 final ExecutorService executorPool = 
				      Executors.newFixedThreadPool(poolSize);    
				    try {
						final List<Future<Double>> valueOfOptions = 
						  executorPool.invokeAll(partitions, 10000, TimeUnit.SECONDS); 
					    for(final Future<Double> valueOfAnOption : valueOfOptions)
					    {  System.out.println("result = " +res);
					      res=res + valueOfAnOption.get(); 
					    }
					} catch (InterruptedException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					} catch (ExecutionException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}
				    executorPool.shutdown();
				   
		 return res/dataChunk;
	}
	/**
	 * @param args
	 */
	/*public static void main(String[] args) {
		// TODO Auto-generated method stub
	  //MonteCarloSimulation mcs = new MonteCarloSimulation("c", 40, 50, 0.06, 0.5, 0.10, 0.45,100000,168);
      /*long t1=System.currentTimeMillis();
      double result = mcs.MonteCarloSim();
      long t2= System.currentTimeMillis();
      long t=t2-t1;
      System.out.println("Result : " + result +" exec Time: " + t);*/
	//}
	
	public void setValues(String callPutFlag,double s,double x,double t,double r,double b,double v,int nSteps,int nSimu, int dataChunk)
	{
		this.callPutFlag=callPutFlag;
		this.s=s;
		this.x=x;
		this.t=t;
		this.r=r;
		this.b=b;
		this.v=v;
		this.nSteps=nSteps;
		this.nSimu=nSimu;
		this.dataChunk=dataChunk;
		this.finish=false;
	}

	@Override
	public void run() {
		this.res=0;
		this.sum=0;
		 long t1=System.currentTimeMillis();
	      double result = this.MonteCarloSim();
	      this.finish=true;
	      long t2= System.currentTimeMillis();
	      this.time=t2-t1;
	      res=result;
	      setChanged();
		  notifyObservers(this);
		
	}
	
	
	public double getRes() {
		return res;
	}

	/**
	 * @return the time
	 */
	public double getTime() {
		return time;
	}

	/**
	 * @return the finish
	 */
	public boolean isFinish() {
		return finish;
	}

}
